Check the wire against BitRouter's published contract, and gate releases by hand - #7
Merged
Merged
Conversation
Publishing becomes a workflow of its own, triggered by a published release and nothing else — deliberately not by a push to `main`. That separation is the whole point. It lets `main` move on its own, because `main` reaches nobody: a Dependabot bump or an automated compatibility fix that goes green can merge itself. The step that does reach somebody stays manual. Collapsing the two into a push trigger would take the safety out of both. It re-runs the build, the test typecheck, and the suite against the tagged tree, refuses a tag that disagrees with package.json, publishes with provenance, and sends prereleases to the `next` dist-tag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The wire tests prove the mapper reads a captured body correctly. They cannot prove the capture still resembles what the server sends, and a stale fixture fails in the worst way available: silently. This package once read `context_window`, a flat `cost`, and a boolean `reasoning`, none of which BitRouter has ever sent, so every model sat at its default window priced at zero and no test minded. It turns out the contract is published. BitRouter Cloud generates an OpenAPI document from the Rust types that serialize the response and serves it unauthenticated, so there is nothing to guess at and no live API key to hold in CI. `schema/models.schema.json` is a vendored copy — vendored rather than fetched, because the suite that gates merges has to be offline and deterministic. The load-bearing assertion is not "does the fixture parse". It is "has the set of fields BitRouter serves changed since somebody last looked". A new field fails once, and the fix is one line in ACKNOWLEDGED recording whether it is mapped or ignored and why. A vanished field fails louder, because that is the direction that goes quietly wrong. Which had already happened: production now serves `hosted`, `open_weights`, `latency`, and `throughput`, three of them required, and the fixtures predated all four. They are recaptured verbatim here — trimmed captures conform to nothing — with claude-sonnet-5 added for the `image_input` path and grok-4.3 for tiered pricing. That last one documents a real gap rather than fixing it. A model can be billed at a steeper rate once input crosses a threshold, for the whole request; `toCost` reports the base bracket, so opencode understates a long prompt to a tiered model. An opencode model entry carries one flat rate per direction and has nowhere to put a ladder, so closing it needs an opencode-side change. Asserted so it stays known. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scheduled contract refresh hands this action a token with contents:write and pull-requests:write, and a moving tag is a standing invitation to whoever can move it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two changes, both in service of one rule: a green pull request should be
safe to merge on its own, and the only step a person has to perform is
cutting a release.
The contract check
test/wire.test.tsproves the mapper reads a captured body correctly. Itcannot prove the capture still resembles what the server sends. That gap
is not hypothetical — this package once read
context_window, a flatcost, and a booleanreasoning, none of which BitRouter has ever sent,so every model sat at its default window priced at zero and no test
minded.
BitRouter Cloud publishes its
GET /v1/modelsresponse schema, generatedfrom the Rust types that serialize it, served unauthenticated at
/openapi.json. So there is nothing to guess at and no live API key inCI.
schema/models.schema.jsonis a vendored copy — vendored rather thanfetched, because the suite that gates merges has to be offline and
deterministic.
The load-bearing assertion is not "does the fixture parse". It is has
the set of fields BitRouter serves changed since somebody last looked.
A new field fails once, and the fix is one line in
ACKNOWLEDGEDrecording whether it is mapped or ignored and why. A vanished field
fails louder, because that is the direction that goes quietly wrong.
It had already drifted. Production now serves
hosted,open_weights,latency, andthroughput— three of them required —and the fixtures predated all four. Recaptured verbatim here, with
claude-sonnet-5added for theimage_inputpath andgrok-4.3fortiered pricing.
That last row documents a real gap rather than fixing it. A model can be
billed at a steeper rate once input crosses a threshold, for the whole
request —
grok-4.3doubles above 200k.toCostreports the basebracket, so opencode understates a long prompt to a tiered model. An
opencode model entry carries one flat rate per direction and has nowhere
to put a ladder, so closing it needs an opencode-side change. Asserted
here so it stays known rather than being rediscovered.
contract.ymlre-vendors the schema on a clock and opens a pull requestcarrying the diff, so a contract change arrives as a reviewable change
with the suite's verdict already on it.
The release gate
publish.yml, triggered by a published release and deliberately not by apush to
main. That separation is what makes auto-merge safe rather thanreckless:
mainmay move on its own becausemainreaches nobody, andthe step that does reach somebody stays manual. It re-runs the build, the
test typecheck, and the suite against the tagged tree, refuses a tag that
disagrees with
package.json, publishes with provenance, and routesprereleases to the
nextdist-tag.Before merging
publish.ymlneeds anNPM_TOKENsecret and annpmenvironment.contract.ymlneedsCONTRACT_PR_TOKEN. A pull request opened withthe default
GITHUB_TOKENdoes not trigger workflows, which wouldleave it with no checks at all.
63 → 73 tests.